import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.LinkedList;

import javax.imageio.ImageIO;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.swing.JPanel;

public class MainScreen extends JPanel implements Runnable {

	// 플레이어 이미지, 배경, 적(대포), 적(지렁이), 폭파이미지
	Image playerImage, background, enemy1, enemy2, explosionImage, coinImage,
			repairImage, heal14Image, repair5Image;
	// 플레이어 캐릭터 클래스
	Hero player;
	// score를 보여주기 위해 변수 선언.
	public int score, statusCount;
	// 점수와 충돌 이미지를 보여주기 위한 폰트 체크~
	private Font gameoverFont, scoreFont, status;

	private final int STATUS_RUNNING = 2;
	private final int STATUS_FAIL = 5;

	// 3-4. Sound
	private Clip bgm, explosionSound, CoinSound, repairSound;

	private int stat = STATUS_RUNNING;

	// ///////////////////////////////////////////////////////////////////////////
	// 0. 적(대포) 이미지를 만드는 구문
	// 미사일을 만들어 배열에 저장
	LinkedList<Enemy> enamys = new LinkedList<Enemy>();
	// ///////////////////////////////////////////////////////////////////////////
	// 1. 적(지렁이) 이미지를 만드는 구문
	// 미사일을 만들어 배열에 저장
	LinkedList<EnemySwam> enamys1 = new LinkedList<EnemySwam>();
	// ///////////////////////////////////////////////////////////////////////////
	// 2. 폭파이펙이미지
	LinkedList<Explosion> explosions = new LinkedList<Explosion>();
	// ////////////////////////////////////////////////////////////////////////////
	// 3. 점수를 올려주는 코인 아이템 생성
	LinkedList<Coin> coins = new LinkedList<Coin>();
	// ////////////////////////////////////////////////////////////////////////////
	// 4. 체력를 올려주는 수리 아이템 생성
	LinkedList<Repair> repairs = new LinkedList<Repair>();
	// ////////////////////////////////////////////////////////////////////////////
	// 5. 체력힐링이펙이미지
	LinkedList<Healing> healings = new LinkedList<Healing>();
	// ////////////////////////////////////////////////////////////////////////////
	// 6. 수리완료이펙이미지
	LinkedList<Repairing> repairings = new LinkedList<Repairing>();
	// ////////////////////////////////////////////////////////////////////////////

	// 적군을 만드는 시간을 조정.
	// 객체형 변수인 경우 0으로 자동 초기화
	// 여기서는 10으로 설정
	long prevStoneCreateTime = 10;
	long CreateTime = 100;
	long CoinTime = 100;

	// 1000은 1초(미리세컨드)로 적을 생성하는 시간 속도를 제어
	final int stoneCreationSpeed = 700;
	final int CreationSpeed = 4000;
	final int CoinSpeed = 2000;

	// ///////////////////////////////////////////////////////////////////////////

	// 화면 출력 구현 부분
	public MainScreen() {

		// 이미지를 불러올때 예외가 발생할 수 있으니 try ~ catch로 묶음
		try {

			// ////////////////////////////////////////////////////////////////////////////////////////////////
			// 1. 히어로 캐릭터 이미지
			// getClass().getResource() : 소스파일이 있는 위치에서 파일을 찾는 기능.
			// URL : 자원(파일)의 위치를 표시하는 클래스
			URL url = getClass().getResource("submarine.png");
			// url = getClass().getResource("player.png");

			// ImageIO.read(URL) : 지정된 경로에 있는 이미지 파일을 읽어서 이미지 객체로 반환.
			Image image2 = ImageIO.read(url);

			playerImage = image2.getScaledInstance(45, 35, Image.SCALE_SMOOTH);

			// ////////////////////////////////////////////////////////////////////////////////////////////////
			// 2. 배경 이미지

			url = getClass().getResource("back.jpg");
			background = ImageIO.read(url);

			// //////////////////////////////////////////////////////////////////////////////////////////////////
			// 3. 적(대포)을 만드는 이미지
			// 적 미사일 이미지를 저장
			url = getClass().getResource("enemies1.png");

			// ImageIO.read(URL) : 지정된 경로에 있는 이미지 파일을 읽어서 이미지 객체로 반환.
			Image image3 = ImageIO.read(url);

			enemy1 = image3.getScaledInstance(43, 35, Image.SCALE_SMOOTH);
			// //////////////////////////////////////////////////////////////////////////////////////////////////
			// 4. 적(지렁이)을 만드는 이미지
			// 적 미사일 이미지를 저장
			url = getClass().getResource("enemies2.png");

			// ImageIO.read(URL) : 지정된 경로에 있는 이미지 파일을 읽어서 이미지 객체로 반환.
			Image image1 = ImageIO.read(url);

			enemy2 = image1.getScaledInstance(40, 27, Image.SCALE_SMOOTH);
			// //////////////////////////////////////////////////////////////////////////////////////////////////
			// 5. 폰트를 그린다.
			// 폰트의 속성 작성.
			scoreFont = new Font("맑은 고딕", Font.BOLD, 15);
			gameoverFont = new Font("HY엽서M", Font.BOLD, 26);

			// //////////////////////////////////////////////////////////////////////////////////////////////////
			// 6. 폭파용 이미지
			// getClass().getResource() : 소스파일이 있는 위치에서 파일을 찾는 기능.
			// 폭파 이벤트 이미지를 저장
			url = getClass().getResource("explosion.png");

			// ImageIO.read(URL) : 지정된 경로에 있는 이미지 파일을 읽어서 이미지 객체로 반환.
			explosionImage = ImageIO.read(url);
			// //////////////////////////////////////////////////////////////////////////////////////////////////
			// 7. 코인 이미지
			// getClass().getResource() : 소스파일이 있는 위치에서 파일을 찾는 기능.
			// 폭파 이벤트 이미지를 저장
			url = getClass().getResource("coin.png");

			// ImageIO.read(URL) : 지정된 경로에 있는 이미지 파일을 읽어서 이미지 객체로 반환.
			Image image5 = ImageIO.read(url);
			coinImage = image5.getScaledInstance(30, 30, Image.SCALE_SMOOTH);
			// //////////////////////////////////////////////////////////////////////////////////////////////////
			// 8. 수리 이미지
			// getClass().getResource() : 소스파일이 있는 위치에서 파일을 찾는 기능.
			// 폭파 이벤트 이미지를 저장
			url = getClass().getResource("repair.png");

			// ImageIO.read(URL) : 지정된 경로에 있는 이미지 파일을 읽어서 이미지 객체로 반환.
			Image image6 = ImageIO.read(url);
			repairImage = image6.getScaledInstance(30, 30, Image.SCALE_SMOOTH);
			// //////////////////////////////////////////////////////////////////////////////////////////////////
			// 9. 체력회복 이펙 이미지
			// getClass().getResource() : 소스파일이 있는 위치에서 파일을 찾는 기능.
			// 폭파 이벤트 이미지를 저장
			url = getClass().getResource("heal14.png");

			// ImageIO.read(URL) : 지정된 경로에 있는 이미지 파일을 읽어서 이미지 객체로 반환.
			Image image7 = ImageIO.read(url);
			heal14Image = image7.getScaledInstance(434, 31, Image.SCALE_SMOOTH);
			// //////////////////////////////////////////////////////////////////////////////////////////////////
			// 10. 잠수함 수리 이펙 이미지
			// getClass().getResource() : 소스파일이 있는 위치에서 파일을 찾는 기능.
			// 폭파 이벤트 이미지를 저장
			url = getClass().getResource("repair5.png");

			// ImageIO.read(URL) : 지정된 경로에 있는 이미지 파일을 읽어서 이미지 객체로 반환.
			Image image8 = ImageIO.read(url);
			repair5Image = image8
					.getScaledInstance(256, 51, Image.SCALE_SMOOTH);
			// //////////////////////////////////////////////////////////////////////////////////////////////////
			// 배경 사운드
			url = getClass().getResource("/submarine-dive.wav");
			AudioInputStream ais = AudioSystem.getAudioInputStream(url); // 오디오
																			// 파일에
			bgm = AudioSystem.getClip(); // 오디오 재생기 생성.
			bgm.open(ais); // 오디오 재생기에 오디오 음원을 삽입.
			bgm.loop(Clip.LOOP_CONTINUOUSLY); // 반복횟수 지정.
			bgm.stop();

			// 배경 사운드
			url = getClass().getResource("/Coin.wav");
			AudioInputStream ais1 = AudioSystem.getAudioInputStream(url); // 오디오
																			// 파일에
			CoinSound = AudioSystem.getClip(); // 오디오 재생기 생성.
			CoinSound.open(ais1); // 오디오 재생기에 오디오 음원을 삽입.
			CoinSound.loop(1); // 반복횟수 지정.
			CoinSound.stop();

			// 배경 사운드
			url = getClass().getResource("/explosion.wav");
			AudioInputStream ais2 = AudioSystem.getAudioInputStream(url); // 오디오
																			// 파일에
			explosionSound = AudioSystem.getClip(); // 오디오 재생기 생성.
			explosionSound.open(ais2); // 오디오 재생기에 오디오 음원을 삽입.
			explosionSound.loop(1); // 반복횟수 지정.
			explosionSound.stop();

			// 배경 사운드
			url = getClass().getResource("/repair.wav");
			AudioInputStream ais3 = AudioSystem.getAudioInputStream(url); // 오디오
																			// 파일에
			repairSound = AudioSystem.getClip(); // 오디오 재생기 생성.
			repairSound.open(ais3); // 오디오 재생기에 오디오 음원을 삽입.
			repairSound.loop(1); // 반복횟수 지정.
			repairSound.stop();

		} catch (Exception ex) {
			ex.printStackTrace();
		}

		// 플레이어이미지를 플레이어 클래스의 이미지에 넘겨줌.
		player = new Hero(playerImage, statusCount, status);

		// 뒷 배경
		setBackground(Color.black);

		// Panel에 KeyBoard 이벤트 수신기 등록
		this.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				super.keyPressed(e);
				player.keyPressedHandler(e);

			}

			@Override
			public void keyReleased(KeyEvent e) {
				super.keyReleased(e);
				player.keyReleasedHandler(e);

			}
		});

		Thread thread = new Thread(this);
		// thread.start()는 run() 메서드를 호출
		thread.start();

		bgm.start();

	} // MainScreen() 메서드를 종료

	// 이미지를 그리자~~
	// 처음 이미지를 띄우는 부분이고 이미지의 변화는 아래 run()메서드에서 구현함.
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);

		// ///////////////////////////////////////////////////////////
		// 0. 배경 이미지를 먼저 그린다. 위치는 0, 0 위치에 그렸음.
		g.drawImage(background, 0, 0, this);

		// ////////////////////////////////////////////////////////////
		// 스코어의 글씨를 출력.
		// 오른쪽 아래에서 출력하여 지속적으로 변화하게 만든다.
		String volumn = String.valueOf(" 득점 : " + score);
		g.setFont(scoreFont);
		g.setColor(Color.WHITE);
		g.drawString(volumn, 20, 20);

		// /////////////////////////////////////////////////////////////

		// Hero 클래스의 render()를 실행.
		// render() 메서드 안에 있는 g.drawImage(image, x, y, null); 를 실행.
		// -> swim.png 이미지를 그린다.
		player.render(g);

		// 미사일 이미지 출력
		// 배열로 만든 이미지를 순차적으로 출력.
		// -> enemy1.gif 이미지를 그린다.
		for (Enemy enemy : enamys) {
			enemy.render(g);
		}

		// 미사일 이미지 출력
		// 배열로 만든 이미지를 순차적으로 출력.
		// -> enemy2.jpg 이미지를 그린다.
		for (EnemySwam enemy : enamys1) {
			enemy.render(g);
		}

		// 폭파 이미지 출력구문
		// 폭파 이미지의 render()를 실행
		for (Explosion explosion : explosions) {
			explosion.render(g);
		}

		// 코인 이미지 출력구문
		// 코인 이미지의 render()를 실행
		for (Coin coin : coins) {
			coin.render(g);
		}

		// 폭파 이미지 출력구문
		// 폭파 이미지의 render()를 실행
		for (Repair repair : repairs) {
			repair.render(g);
		}

		// 코인 먹을때 이펙 이미지 출력구문
		// 코인 먹을때 이펙 이미지의 render()를 실행
		for (Healing healEffect : healings) {
			healEffect.render(g);
		}

		// 수리아이콘 먹을때 이펙 이미지 출력구문
		// 수리아이콘 먹을때 이펙 이미지의 render()를 실행
		for (Repairing repairEffect : repairings) {
			repairEffect.render(g);
		}

		if (stat == STATUS_FAIL) {
			Graphics2D g2d = (Graphics2D) g;
			// 현재 폰트 기준으로 지정된 문자열이 출력될 사각형 반환
			String volumn1 = String.valueOf("GAME OVER");
			String volumn2 = String.valueOf("당신의 점수는 " + score + "...");

			Rectangle2D rectFont = g2d.getFontMetrics().getStringBounds(
					volumn2, g2d);
			g.setFont(gameoverFont);
			g.setColor(Color.LIGHT_GRAY);
			g.drawString(volumn1, 220, 120);
			int x = (Constants.SCREEN_WIDTH - (int) rectFont.getWidth()) / 2 - 100;
			int y = (Constants.SCREEN_HEIGHT - (int) rectFont.getHeight()) / 2;
			g2d.drawString(volumn2, x, y);
		}

	} // paintComponent(Graphics g) 메서드를 종료.

	// run()을 실행. 이미지(화면)의 변화를 표현해주는 부분..
	@Override
	public void run() {

		// true일때 내부 내용이 계속 실행됨.
		while (true) {

			// ////////////////////////////////////////////////////////////////
			// Hero 클래스의 update()를 실행
			// Hero클래스에서 변경된 데이터를 반환받아 update();
			player.update();

			// ///////////////////////////////////////////////////////////////
			// 적(대포)을 다시 업데이트함
			// 배열이라 for문으로 배열 크기만큼을 enemy에 넣는다.
			for (int i = enamys.size() - 1; i >= 0; i--) {
				Enemy enemy = enamys.get(i);
				enemy.update();

				// 충돌을 위해 active를 둠.
				// active가 false면 이미지 지우기
				if (enemy.active == false) {
					enamys.remove(enemy);
				}
			}

			// 적(지렁이)를 다시 업데이트함
			// 배열이라 for문으로 다시 enemy1에 넣는다
			for (int i = enamys1.size() - 1; i >= 0; i--) {
				EnemySwam enemy1 = enamys1.get(i);
				enemy1.update();

				// 충돌을 위해 active를 둠.
				// active가 false면 이미지 지우기
				if (enemy1.active == false) {
					enamys1.remove(enemy1);
				}
			}

			// 코인를 다시 업데이트함
			// 배열이라 for문으로 다시 enemy1에 넣는다
			for (int i = coins.size() - 1; i >= 0; i--) {
				Coin coin = coins.get(i);
				coin.update();

				// 충돌을 위해 active를 둠.
				// active가 false면 이미지 지우기
				if (coin.active == false) {
					coins.remove(coin);
				}
			}

			// 수리아이템를 다시 업데이트함
			// 배열이라 for문으로 다시 enemy1에 넣는다
			for (int i = repairs.size() - 1; i >= 0; i--) {
				Repair repair1 = repairs.get(i);
				repair1.update();

				// 충돌을 위해 active를 둠.
				// active가 false면 이미지 지우기
				if (repair1.active == false) {
					repairs.remove(repair1);
				}
			}

			// 생성-1. 적 미사일을 생성
			long currentTime = System.currentTimeMillis();
			// 1초마다 한번씩 stone을 생성
			// prevStoneCreateTime 는 스톤을 만든 시간
			// stoneCreationSpeed 는 스톤을 만드는 시간
			if (prevStoneCreateTime + stoneCreationSpeed < currentTime) {
				// 스톤 이미지를 불러옴
				Enemy enemy = new Enemy(enemy1);
				// 스톤의 LinkedList에 이미지를 넣는다.
				enemy.active = true;
				enamys.add(enemy);

				EnemySwam enemy1 = new EnemySwam(enemy2);
				// 스톤의 LinkedList에 이미지를 넣는다.
				enemy1.active = true;
				enamys1.add(enemy1);

				prevStoneCreateTime = currentTime;
			}

			// 수리아이템을 출력하는 제어(시간등)
			if (CreateTime + CreationSpeed < currentTime) {

				Repair repair = new Repair(repairImage);
				// 스톤의 LinkedList에 이미지를 넣는다.
				repair.active = true;
				repairs.add(repair);

				CreateTime = currentTime;
			}

			// 코인아이템을 출력하는 제어(시간등)
			if (CoinTime + CoinSpeed < currentTime) {

				Coin coin = new Coin(coinImage);
				// 스톤의 LinkedList에 이미지를 넣는다.
				coin.active = true;
				coins.add(coin);

				CoinTime = currentTime;
			}

			// 이미지 출력(폭파이미지) 후에 이미지 제거
			// 충돌 후 폭파용 이미지 제거
			for (int i = explosions.size() - 1; i >= 0; i--) {
				explosions.get(i).update();
				if (explosions.get(i).active == false) {
					explosions.remove();
				}
			}

			// 이미지 출력(힐링이미지) 후에 이미지 제거
			// 충돌 후 힐링용 이미지 제거
			for (int i = healings.size() - 1; i >= 0; i--) {
				healings.get(i).update();
				if (healings.get(i).active == false) {
					healings.remove();
				}
			}

			// 이미지 출력(점수이미지) 후에 이미지 제거
			// 충돌 후 점수용 이미지 제거
			for (int i = repairings.size() - 1; i >= 0; i--) {
				repairings.get(i).update();
				if (repairings.get(i).active == false) {
					repairings.remove();
				}
			}

			// /////////////////////////////////////////////////////////////////////////////////////
			// 충돌 되는 비교 연산.
			// hero와 적(대포)와 충돌하면 적을 없애고 100점을 차감한다.

			for (int i = enamys.size() - 1; i >= 0; i--) {
				Rectangle rect1 = player.getArea();
				Rectangle rect2 = enamys.get(i).getArea();

				// 첫번째 사각형과 두번째 사각형을 비교.
				// 스톤과 미사일을 false로 목록에서 제거
				// 스톤과 미사일을 remove 처리.

				if (rect1.intersects(rect2)) {
					// 충돌 확인 (
					enamys.get(i).active = false;
					enamys.remove(i);
					
					explosionSound.start();

					// 적(대포)점수를 100점 깍는다
					Hero.statusCount = Hero.statusCount - 10;

					// 폭파용 객체 생성
					// 마지막에 있는 6은 이미지의 6등분
					Explosion exp = new Explosion(explosionImage, rect2.x
							+ (rect2.width - explosionImage.getWidth(null))
							/ 18, rect2.y
							+ (rect2.height - explosionImage.getHeight(null))
							/ 2, 9);
					explosions.add(exp);

					break;
				}
			}
			// ////////////////////////////////////////////////////////////////////////
			// 캐릭터와 적(지렁이)의 충돌 체크.
			// 충돌되면 적(지렁이)를 없애고 200점을 차감한다.
			for (int i = enamys1.size() - 1; i >= 0; i--) {
				Rectangle rect1 = player.getArea();
				Rectangle rect2 = enamys1.get(i).getArea();

				// 첫번째 사각형과 두번째 사각형을 비교.
				// 스톤과 미사일을 false로 목록에서 제거
				// 스톤과 미사일을 remove 처리.

				if (rect1.intersects(rect2)) {
					// 충돌 확인 (
					enamys1.get(i).active = false;
					enamys1.remove(i);

					// 적(대포)점수를 100점 깍는다
					Hero.statusCount = Hero.statusCount - 20;
					
					explosionSound.start();

					// 폭파용 객체 생성
					// 마지막에 있는 6은 이미지의 6등분
					Explosion exp = new Explosion(explosionImage, rect2.x
							+ (rect2.width - explosionImage.getWidth(null))
							/ 18, rect2.y
							+ (rect2.height - explosionImage.getHeight(null))
							/ 2, 9);
					explosions.add(exp);

					break;
				}
			}

			// ////////////////////////////////////////////////////////////////////////
			// 캐릭터와 코인의 충돌 체크.
			// 충돌되면 코인를 없애고 100점을 올린다
			for (int i = coins.size() - 1; i >= 0; i--) {
				Rectangle rect1 = player.getArea();
				Rectangle rect2 = coins.get(i).getArea();

				// 첫번째 사각형과 두번째 사각형을 비교.
				// 스톤과 미사일을 false로 목록에서 제거
				// 스톤과 미사일을 remove 처리.

				if (rect1.intersects(rect2)) {
					// 충돌 확인 (
					coins.get(i).active = false;
					coins.remove(i);

					// 적(대포)점수를 100점 깍는다
					score = score + 100;
					
					CoinSound.start();

					// 폭파용 객체 생성
					// 마지막에 있는 6은 이미지의 6등분
					Healing exp = new Healing(heal14Image, rect1.x + 8,
							rect1.y - 3, 14);
					healings.add(exp);

					break;
				}
			}
			// ////////////////////////////////////////////////////////////////////////
			// 캐릭터와 수리아이템의 충돌 체크.
			// 충돌되면 수리아이템를 없애고 20점을 추가한다.
			for (int i = repairs.size() - 1; i >= 0; i--) {
				Rectangle rect1 = player.getArea();
				Rectangle rect2 = repairs.get(i).getArea();

				// 첫번째 사각형과 두번째 사각형을 비교.
				// 스톤과 미사일을 false로 목록에서 제거
				// 스톤과 미사일을 remove 처리.

				if (rect1.intersects(rect2)) {
					// 충돌 확인 (
					repairs.get(i).active = false;
					repairs.remove(i);
					
					repairSound.start();

					// 적(대포)점수를 100점 깍는다
					Hero.statusCount = Hero.statusCount + 10;

					if (Hero.statusCount >= 100) {
						Hero.statusCount = 100;
					}

					// 폭파용 객체 생성
					// 마지막에 있는 6은 이미지의 6등분
					Repairing exp = new Repairing(repair5Image, rect1.x - 8,
							rect1.y - 8, 5);
					repairings.add(exp);

					break;
				}
			}

			// /////////////////////////////////////////////////////////////////////////////////

			if (stat == STATUS_RUNNING) {
				update();
			} else if (stat == STATUS_FAIL) {
				break;
			}

			// 갱신된 데이터 화면 출력 ( paintComponent(Graphics g) 을 호출 )
			repaint();

			// 프레임을 인위로 조정하기 위해 try~ catch 추가.
			try {
				Thread.sleep(30);
			} catch (Exception ex) {
			}

		}

	}

	private void update() {
		if (Hero.statusCount <= 0) {
			Hero.statusCount = 0;
			stat = STATUS_FAIL;
		}
	}

}
